<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        html, body {
            margin: 0;
            height: 100%;
        }

        canvas {
            display: block;
        }

        #blocker {
            position: absolute;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
        }

        #instructions {
            width: 100%;
            height: 100%;
            display: -webkit-box;
            display: -moz-box;
            -webkit-box-orient: horizontal;
            -moz-box-orient: horizontal;
            -webkit-box-pack: center;
            -moz-box-pack: center;
            -webkit-box-align: center;
            -moz-box-align: center;
            color: #ffffff;
            text-align: center;
            cursor: pointer;
        }

    </style>
</head>

<body>
<div id="blocker">

    <div id="instructions">
        <span style="font-size:40px">点击屏幕开始</span>
        <br/>
        <br/>
        (W, A, S, D = 移动, SPACE = 跳跃, MOUSE = 移动视角)
    </div>

</div>
</body>
<script src="libs/js/three.js"></script>
<script src="libs/js/GLTFLoader.js"></script>
<script src="libs/js/PointerLockControls.js"></script>
<script>

    //移动相关的变量
    // import * as THREE from "../libs/js/three";

    let ws;
    let usrModel = {};
    let moveLeft = false;
    let moveRight = false;
    let canJump = false;
    let jumping = false; //处理一直按着空格连续跳的问题
    let moveForward = false;
    let moveBackward = false;
    let controlsEnabled = false;
    const speed = 800; //控制器移动速度
    const upSpeed = 200; //控制跳起时的速度
    let velocity = new THREE.Vector3(); //移动速度变量
    let direction = new THREE.Vector3(); //移动的方向变量
    let rotation = new THREE.Vector3(); //当前的相机朝向
    let renderer, camera, scene, light, controls;
    const blocker = document.getElementById('blocker');//是否锁定页面的相关
    const instructions = document.getElementById('instructions');
    let localUsrName = (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
    let upRay = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, 1, 0), 0, 20);//声明射线
    let horizontalRay = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(), 0, 20);
    let downRay = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, -1, 0), 0, 20);


    function initScene() {
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xaaaaaa);
        scene.background = new THREE.CubeTextureLoader().setPath('./model/models/').load(
            [
                'px.jpg',
                'nx.jpg',
                'py.jpg',
                'ny.jpg',
                'pz.jpg',
                'nz.jpg'
            ]
        );
    }

    function initCamera() {
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 6000);
        scene.add(camera);
    }

    //初始化控制器
    function initControls() {
        controls = new THREE.PointerLockControls(camera);
        controls.getObject().position.y = 50;
        controls.getObject().position.z = 3500;
        controls.getObject().position.x = 500;
        scene.add(controls.getObject());
        const onKeyDown = function (event) {
            switch (event.keyCode) {
                case 38: // up
                case 87: // w
                    moveForward = true;
                    break;
                case 37: // left
                case 65: // a
                    moveLeft = true;
                    break;
                case 40: // down
                case 83: // s
                    moveBackward = true;
                    break;
                case 39: // right
                case 68: // d
                    moveRight = true;
                    break;
                case 32: // space
                    if (canJump && !jumping) velocity.y += upSpeed;
                    canJump = false;
                    jumping = true;
                    break;
            }
        };
        const onKeyUp = function (event) {
            switch (event.keyCode) {
                case 38: // up
                case 87: // w
                    moveForward = false;
                    break;
                case 37: // left
                case 65: // a
                    moveLeft = false;
                    break;
                case 40: // down
                case 83: // s
                    moveBackward = false;
                    break;
                case 39: // right
                case 68: // d
                    moveRight = false;
                    break;
            }
        };
        document.addEventListener('keydown', onKeyDown, false);
        document.addEventListener('keyup', onKeyUp, false);
    }

    //初始化鼠标锁
    let clock_start = new THREE.Clock();
    let time = 0;
    function initPointerLock() {
        //实现鼠标锁定的教程地址 http://www.html5rocks.com/en/tutorials/pointerlock/intro/
        const havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
        if (havePointerLock) {
            const element = document.body;
            const pointerlockchange = function () {
                if (document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element) {
                    controlsEnabled = true;
                    controls.enabled = true;
                    blocker.style.display = 'none';
                } else {
                    controls.enabled = false;
                    blocker.style.display = 'block';
                    instructions.style.display = '';
                }
            };
            const pointerlockerror = function () {
                instructions.style.display = '';
            };
            // 监听变动事件
            document.addEventListener('pointerlockchange', pointerlockchange, false);
            document.addEventListener('mozpointerlockchange', pointerlockchange, false);
            document.addEventListener('webkitpointerlockchange', pointerlockchange, false);
            document.addEventListener('pointerlockerror', pointerlockerror, false);
            document.addEventListener('mozpointerlockerror', pointerlockerror, false);
            document.addEventListener('webkitpointerlockerror', pointerlockerror, false);
            instructions.addEventListener('click', function () {
                time += clock_start.getDelta();
                // if(time<3)
                //     return;
                instructions.style.display = 'none';
                // 锁定鼠标光标
                element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
                element.requestPointerLock();
            }, false);
        } else {
            instructions.innerHTML = '你的浏览器不支持相关操作，请更换浏览器';
        }
    }

    //初始化灯光
    function initLight() {
        scene.add(new THREE.AmbientLight("#111111"));
        light = new THREE.PointLight(0xffffff, 1);//new THREE.DirectionalLight("#ffffff");
        light.position.set(-6000, 0, 0);
        //告诉平行光需要开启阴影投射
        scene.add(light);
        const light_x = [1500, -1500, 1500, -1500];
        const light_z = [1500, -1500, 1500, -1500];
        for (let i = 0; i < 4; i++) {
            let tem_light = new THREE.DirectionalLight("#aaaaaa", 0.15);
            tem_light.position.set(light_x[i], 1000, light_z[i]);
            scene.add(tem_light);
        }
    }

    //初始化模型
    function initModel() {
        let material_grass = new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('./model/models/grass.jpg')});
        let mapPlane = new THREE.PlaneGeometry(9000, 9000, 4, 4);
        let mesh_plane = new THREE.Mesh(mapPlane, material_grass);
        mesh_plane.position.set(0, 0, 0);
        mesh_plane.receiveShadow = true;
        mesh_plane.rotation.set(-0.5 * Math.PI, 0, 0);
        scene.add(mesh_plane);
        //创建迷宫
        const mazeArray = [
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
            [1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1],
            [1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1],
            [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],
            [1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1],
            [1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1],
            [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
            [1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],
            [1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1],
            [1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
            [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]];
        const min_x = 1350;
        let min_z = -1350;
        for (let i = 0; i < 15; i++) {
            min_z = min_z - 50;
            for (let j = 0; j < 15; j++) {
                if (mazeArray[i][j] === 1) {
                    const geo = new THREE.CubeGeometry(50, 100, 50);
                    const material_logo = new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('./model/models/fudan.jpg')});
                    let mesh = new THREE.Mesh(geo, material_logo);
                    mesh.position.x = min_x + j * 50;
                    mesh.position.y = 50;
                    mesh.position.z = min_z;
                    scene.add(mesh);
                }
            }
        }

        let loader = new THREE.GLTFLoader();
        const sword = './model/models/swor/scene.gltf';
        loader.load(sword, function (gltf) {
            gltf.scene.scale.set(20, 20, 20);
            gltf.scene.position.x = -1500;
            gltf.scene.position.y = 500;
            gltf.scene.position.z = -1500;
            gltf.scene.rotation.x = 2;
            gltf.scene.rotation.z = 5;
            scene.add(gltf.scene);
        });

        //塔
        for (let i = 0; i < 5; i++) {
            for (let j = 0; j < 5; j++) {
                const tower = './model/models/tower/scene.gltf';
                loader.load(tower, function (gltf) {
                    gltf.scene.scale.set(3, 3, 3);
                    gltf.scene.position.x = -500 - j * 1000;
                    gltf.scene.position.y = 3;
                    gltf.scene.position.z = 500 + i * 1000;
                    scene.add(gltf.scene);
                });
            }
        }
        //房子
        for (let i = 0; i < 3; i++) {
            for (let j = 0; j < 3; j++) {
                const house = './model/models/house/scene.gltf';
                loader.load(house, function (gltf) {
                    gltf.scene.scale.set(15, 15, 15);
                    gltf.scene.position.x = 500 + j * 1000;
                    gltf.scene.position.y = 3;
                    gltf.scene.position.z = 500 + i * 1000;
                    scene.add(gltf.scene);
                });
            }
        }
        //草
        for (let i = 0; i < 100; i++) {
            const grass = './model/models/grass/scene.gltf';
            loader.load(grass, function (gltf) {
                gltf.scene.scale.set(0.1, 0.1, 0.1);
                gltf.scene.position.x = 3000 - Math.random() * 3000;//100 - Math.random() * 200;
                gltf.scene.position.y = 0;
                gltf.scene.position.z = 3000 - Math.random() * 6000;//100 - Math.random() * 200;
                gltf.scene.receiveShadow = true;
                gltf.scene.castShadow = true;
                scene.add(gltf.scene);
            });
        }
        //创建天空悬浮物
        creatSkyCube(50, 50, 50, 100);
    }

    function initRender() {
        renderer = new THREE.WebGLRenderer({antialias: true});
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.sortObjects = false;
        //告诉渲染器需要阴影效果
        document.body.appendChild(renderer.domElement);
    }

    //渲染
    let clock = new THREE.Clock();

    function render() {
        if (controlsEnabled === true) {
            //获取到控制器对象
            let control = controls.getObject();
            //获取刷新时间
            let delta = clock.getDelta();

            //velocity每次的速度，为了保证有过渡
            velocity.x -= velocity.x * 10.0 * delta;
            velocity.z -= velocity.z * 10.0 * delta;
            velocity.y -= 9.8 * 100.0 * delta; // 默认下降的速度

            //获取当前按键的方向并获取朝哪个方向移动
            direction.z = Number(moveForward) - Number(moveBackward);
            direction.x = Number(moveLeft) - Number(moveRight);
            //将法向量的值归一化
            direction.normalize();
            //判断是否接触到了模型
            rotation.copy(control.getWorldDirection(new THREE.Vector3()).multiply(new THREE.Vector3(-1, 0, -1)));

            //判断鼠标按下的方向
            let m = new THREE.Matrix4();
            if (direction.z > 0) {
                if (direction.x > 0) {
                    m.makeRotationY(Math.PI / 4);
                } else if (direction.x < 0) {
                    m.makeRotationY(-Math.PI / 4);
                } else {
                    m.makeRotationY(0);
                }
            } else if (direction.z < 0) {
                if (direction.x > 0) {
                    m.makeRotationY(Math.PI / 4 * 3);
                } else if (direction.x < 0) {
                    m.makeRotationY(-Math.PI / 4 * 3);
                } else {
                    m.makeRotationY(Math.PI);
                }
            } else {
                if (direction.x > 0) {
                    m.makeRotationY(Math.PI / 2);
                } else if (direction.x < 0) {
                    m.makeRotationY(-Math.PI / 2);
                }
            }
            //给向量使用变换矩阵
            rotation.applyMatrix4(m);
            horizontalRay.set(control.position, rotation);
            let tem_array = [];
            const length = scene.children.length;
            let j = 0;
            for (let i = 0; i < length; i++) {
                if (scene.children[i].name === "person")
                    continue;
                tem_array[j] = scene.children[i];
                j++;
            }
            const horizontalIntersections = horizontalRay.intersectObjects(tem_array, true);
            const horOnObject = horizontalIntersections.length > 0;

            //判断移动方向修改速度方向
            if (!horOnObject) {
                if (moveForward || moveBackward) velocity.z -= direction.z * speed * delta;
                if (moveLeft || moveRight) velocity.x -= direction.x * speed * delta;
            }

            //复制相机的位置
            downRay.ray.origin.copy(control.position);
            //获取相机靠下10的位置
            downRay.ray.origin.y -= 10;
            //判断是否停留在了立方体上面
            const intersections = downRay.intersectObjects(scene.children, true);
            const onObject = intersections.length > 0;
            //判断是否停在了立方体上面
            if (onObject === true) {
                velocity.y = Math.max(0, velocity.y);
                canJump = true;
                jumping = false;
            }
            //根据速度值移动控制器
            control.translateX(velocity.x * delta);
            control.translateY(velocity.y * delta);
            control.translateZ(velocity.z * delta);
            //保证控制器的y轴在10以上
            if (control.position.y < 10) {
                velocity.y = 0;
                control.position.y = 10;
                canJump = true;
                jumping = false;
            }

            //向上射线判断
            upRay.ray.origin.copy(control.position);
            upRay.ray.origin.y += 10;
            const inter = upRay.intersectObjects(scene.children, true);
            const underObject = inter.length > 0;
            canJump = !underObject;
            if (rotation.y !== 0)
                alert(rotation.y);
            let data = {
                "name": localUsrName,
                "type": "move",
                "pos": [control.position.x,
                    control.position.y-27, control.position.z],
                "rotation": [rotation.x, rotation.y, rotation.z]
            };
            ws.send(JSON.stringify(data));
        }

    }

    function animate() {
        lightChang();
        //更新控制器
        render();
        renderer.render(scene, camera);
        requestAnimationFrame(animate);
    }

    function draw() {
        initPointerLock();
        initRender();
        initScene();
        initCamera();
        initLight();
        initModel();
        initControls();
        animate();
        window.onresize = onWindowResize;
    }

    let sunUp_first = true;
    let forward_first = true;

    function lightChang() {
        light.position.y += sunUp_first ? 5 : -5;
        light.position.x += forward_first ? 10 : -10;
        if (Math.abs(light.position.y) === 3000)
            sunUp_first = !sunUp_first;
        if (Math.abs(light.position.x) === 6000)
            forward_first = !forward_first;
    }

    //窗口变动触发的函数
    function onWindowResize() {

        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        render();
        renderer.setSize(window.innerWidth, window.innerHeight);

    }

    function createPerson(pos, rotation, name) {
        let loader = new THREE.GLTFLoader();
        const man = './model/models/man/scene.gltf';
        loader.load(man, function (gltf) {
            gltf.scene.scale.set(5, 5, 5);
            gltf.scene.position.set(pos[0], pos[1], pos[2]);
            gltf.scene.name = "person";
            usrModel[name] = gltf.scene;//加入全局model数组中
            setRotation(rotation, name);
            scene.add(usrModel[name]);
        });
    }

    function setRotation(rotation, name) {
        if (rotation[0] === 0)
            usrModel[name].rotation.y = rotation[2] > 0 ? 0 : 3;
        else if (rotation[2] > 0)
            usrModel[name].rotation.y = rotation[0] * 3 / 2;
        else if (rotation[0] < 0)
            usrModel[name].rotation.y = -3 - rotation[0] * 3 / 2;
        else
            usrModel[name].rotation.y = 3 - rotation[0] * 3 / 2;
    }

    //创建浮动方块
    function creatSkyCube(width, length, height, amount) {
        const geo = new THREE.CubeGeometry(width, length, height);
        const material1 = new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('./model/models/grass.jpg')});
        for (let i = 0; i < amount; i++) {
            let mesh = new THREE.Mesh(geo, material1);
            mesh.position.x = 6000 - Math.random() * 12000;
            mesh.position.y = 500 + Math.random() * 2000;
            mesh.position.z = 6000 - Math.random() * 12000;
            scene.add(mesh);
        }
    }

    window.onload = function () {
        draw();
        if ("WebSocket" in window) {
            // 打开一个 web socket
            ws = new WebSocket("ws://3.92.133.239:2333");
            ws.onopen = function () {
                // 发送添加本地用户消息
                let data = {
                    "name": localUsrName,
                    "type": "add",
                    "pos": [controls.getObject().position.x,
                        controls.getObject().position.y-27, controls.getObject().position.z],
                    "rotation": [rotation.x, rotation.y, rotation.z]
                };

                ws.send(JSON.stringify(data));//发送JSON字符串
            };

            ws.onmessage = function (evt) {
                //接受消息并更新model，添加或者移动model，不更新自身的消息
                let messageJson = JSON.parse(evt.data);
                if (!(messageJson["name"] === localUsrName)) {
                    //判断消息的类型：添加、删除、移动
                    let pos = messageJson["pos"], rotation = messageJson["rotation"];
                    if (messageJson["type"] === "add") {
                        //添加新的model
                        if (!usrModel.hasOwnProperty(messageJson["name"])) {
                            createPerson(pos, rotation, messageJson["name"]);
                        }
                    } else if (messageJson["type"] === "delete") {
                        //移除下线的model
                        scene.remove(usrModel[messageJson["name"]]);
                        delete usrModel[messageJson["name"]];
                    } else if (messageJson["type"] === "move") {
                        //更改移动的model位置
                        usrModel[messageJson["name"]].position.set(pos[0], pos[1], pos[2]);
                        setRotation(rotation, messageJson["name"]);
                    }
                }
            };

            ws.onclose = function () {
                //发送删除model的消息
                let data = {
                    "name": localUsrName,
                    "type": "delete",
                    "pos": [controls.getObject().position.x,
                        controls.getObject().position.y-27, controls.getObject().position.z],
                    "rotation": [rotation.x, rotation.y, rotation.z]
                };
                ws.send(JSON.stringify(data));//发送JSON字符串
            };
        } else {
            alert("no WebSocket!");
        }
    };
</script>
</html>